import VueApollo from 'vue-apollo';
import {
  GlAccordionItem,
  GlSkeletonLoader,
  GlFormCheckbox,
  GlButton,
  GlPopover,
  GlSprintf,
  GlLink,
} from '@gitlab/ui';
import Vue, { nextTick } from 'vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import ExplainVulnerabilityPrompt from 'ee/vulnerabilities/components/explain_vulnerability/explain_vulnerability_prompt.vue';
import explainVulnerabilityPromptQuery from 'ee/security_dashboard/graphql/queries/explain_vulnerability_prompt.query.graphql';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { scrollToElement } from '~/lib/utils/common_utils';

jest.mock('~/lib/utils/common_utils');

Vue.use(VueApollo);

const PROMPT_WITHOUT_CODE = 'non-code prompt';
const PROMPT_WITH_CODE = 'code prompt';

describe('Explain Vulnerability Prompt component', () => {
  let wrapper;

  const getMutationResponse = ({
    promptWithCode = PROMPT_WITH_CODE,
    promptWithoutCode = PROMPT_WITHOUT_CODE,
    potentialSecretsInCode = false,
  } = {}) =>
    jest.fn().mockResolvedValue({
      data: {
        explainVulnerabilityPrompt: {
          promptWithCode,
          promptWithoutCode,
          presubmissionChecks: { potentialSecretsInCode },
        },
      },
    });

  const createWrapper = ({
    mutationResponse = getMutationResponse(),
    isDrawerOpen = false,
  } = {}) => {
    wrapper = shallowMountExtended(ExplainVulnerabilityPrompt, {
      apolloProvider: createMockApollo([[explainVulnerabilityPromptQuery, mutationResponse]]),
      propsData: { vulnerabilityGraphqlId: 'gid://gitlab/Vulnerability/1', isDrawerOpen },
      stubs: { GlSprintf },
    });
  };

  const findAccordionItem = () => wrapper.findComponent(GlAccordionItem);
  const findSendCodeCheckbox = () => wrapper.findComponent(GlFormCheckbox);
  const findLoadingBlock = () => wrapper.findByTestId('loading-block');
  const findLoadingError = () => wrapper.findByTestId('loading-error');
  const findPromptBlock = () => wrapper.findByTestId('prompt-block');
  const findPopover = () => wrapper.findComponent(GlPopover);
  const findInformationIcon = () => wrapper.findComponent(GlButton);
  const findSecretDetectedWarning = () => wrapper.findByTestId('secret-detected-warning');

  describe('clickable accordion item', () => {
    it('shows the "Show Prompt" clickable item', () => {
      createWrapper();

      expect(findAccordionItem().props('title')).toBe('Show prompt');
    });

    it('shows "Hide Prompt" when the prompt is visible', async () => {
      createWrapper();
      await waitForPromises();
      findAccordionItem().vm.$emit('input', true);
      await nextTick();

      expect(findAccordionItem().props('title')).toBe('Hide prompt');
    });
  });

  describe('send code with prompt checkbox', () => {
    it('is indeterminate and disabled when the prompt is loading', () => {
      createWrapper();

      expect(findSendCodeCheckbox().attributes()).toMatchObject({
        disabled: 'true',
        indeterminate: 'true',
      });
    });

    it('shows the information icon', () => {
      createWrapper();

      expect(findInformationIcon().props()).toMatchObject({
        icon: 'information-o',
        variant: 'link',
      });
    });

    it('shows the popover', () => {
      createWrapper();
      expect(findPopover().props()).toMatchObject({
        title: ExplainVulnerabilityPrompt.i18n.popoverTitle,
        showCloseButton: '',
      });
      expect(findPopover().attributes('delay')).toBe('0');
      expect(findPopover().props('target')()).toBe(findInformationIcon().element);
    });

    it.each`
      phrase                      | promptWithCode      | checked      | disabled
      ${'checked and enabled'}    | ${PROMPT_WITH_CODE} | ${'true'}    | ${undefined}
      ${'unchecked and disabled'} | ${''}               | ${undefined} | ${'true'}
    `(
      'is $phrase when prompt with code is "$promptWithCode"',
      async ({ promptWithCode, checked, disabled }) => {
        createWrapper({ mutationResponse: getMutationResponse({ promptWithCode }) });
        await waitForPromises();

        expect(findSendCodeCheckbox().attributes('checked')).toBe(checked);
        expect(findSendCodeCheckbox().attributes('disabled')).toBe(disabled);
      },
    );

    it('is disabled if the drawer is open', () => {
      createWrapper({ isDrawerOpen: true });

      expect(findSendCodeCheckbox().attributes('disabled')).toBe('true');
    });
  });

  describe('prompt is loading', () => {
    beforeEach(createWrapper);

    it('shows the skeleton loader', () => {
      expect(findLoadingBlock().findComponent(GlSkeletonLoader).exists()).toBe(true);
    });

    it('does not show anything else', () => {
      expect(findLoadingError().exists()).toBe(false);
      expect(findPromptBlock().exists()).toBe(false);
      expect(findSecretDetectedWarning().exists()).toBe(false);
    });
  });

  describe('prompt loading error', () => {
    beforeEach(async () => {
      createWrapper({ mutationResponse: jest.fn().mockRejectedValue() });
      await waitForPromises();
    });

    it('shows the error alert', () => {
      expect(findLoadingError().exists()).toBe(true);
    });

    it('does not show anything else', () => {
      expect(findLoadingBlock().exists()).toBe(false);
      expect(findPromptBlock().exists()).toBe(false);
      expect(findSecretDetectedWarning().exists()).toBe(false);
    });
  });

  describe('prompt loaded', () => {
    it.each`
      checked  | expectedPrompt
      ${true}  | ${PROMPT_WITH_CODE}
      ${false} | ${PROMPT_WITHOUT_CODE}
    `(
      'shows the prompt "$expectedPrompt" when the checkbox is $checked',
      async ({ checked, expectedPrompt }) => {
        createWrapper();
        await waitForPromises();
        findSendCodeCheckbox().vm.$emit('input', checked);
        await nextTick();

        expect(findPromptBlock().text()).toBe(expectedPrompt);
      },
    );

    it.each`
      phrase             | potentialSecretsInCode
      ${'shows'}         | ${true}
      ${'does not show'} | ${false}
    `(
      '$phrase secret detected warning when potentialSecretsInCode is $potentialSecretsInCode',
      async ({ potentialSecretsInCode }) => {
        createWrapper({ mutationResponse: getMutationResponse({ potentialSecretsInCode }) });
        await waitForPromises();

        expect(findSecretDetectedWarning().exists()).toBe(potentialSecretsInCode);
      },
    );

    it.each`
      phrase        | potentialSecretsInCode | checked
      ${'checks'}   | ${true}                | ${undefined}
      ${'unchecks'} | ${false}               | ${'true'}
    `(
      '$phrase the send code with prompt checkbox by default when potentialSecretsInCode is $potentialSecretsInCode',
      async ({ potentialSecretsInCode, checked }) => {
        createWrapper({ mutationResponse: getMutationResponse({ potentialSecretsInCode }) });
        await waitForPromises();

        expect(findSendCodeCheckbox().attributes('checked')).toBe(checked);
      },
    );

    describe('secret detected warning', () => {
      beforeEach(() => {
        createWrapper({ mutationResponse: getMutationResponse({ potentialSecretsInCode: true }) });
        return waitForPromises();
      });

      it('shows the warning with the correct variant and text', () => {
        expect(findSecretDetectedWarning().props()).toMatchObject({
          variant: 'warning',
          title: 'Warning: possible secrets detected',
        });
        expect(findSecretDetectedWarning().text()).toBe(
          'GitLab has identified sensitive strings in the code snippet for the AI prompt, indicating a possible leaked secret. Please review your code before utilizing the Explain This Vulnerability feature. If you still wish to proceed and send the code to the AI, click the checkbox below.',
        );
      });

      it('jumps to source code when code link is clicked', () => {
        findSecretDetectedWarning().findComponent(GlLink).vm.$emit('click');

        expect(scrollToElement).toHaveBeenCalledWith('#vulnerability-details-location');
      });

      it('closes the alert when the x icon is clicked', async () => {
        findSecretDetectedWarning().vm.$emit('dismiss');
        await nextTick();

        expect(findSecretDetectedWarning().exists()).toBe(false);
      });
    });
  });
});
